/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    Copyright (C) 2004
 *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
 *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 */

package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;

import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
import weka.core.FastVector;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.core.Utils;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JComponent;

/**
 * <p>
 * GraphPanel.java <br/>
 * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
 * Date: Sep 16, 2004 <br/>
 * Time: 10:28:19 AM <br/>
 * $ Revision 1.4 $ <br/>
 * </p>
 * 
 * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 * @version $Revision: 1.4 $
 */
public class GraphPanel extends JComponent implements RevisionHandler {

	/** for serialization */
	private static final long serialVersionUID = 7917937528738361470L;

	/**
	 * Holds the clustering results
	 */
	private FastVector resultVector;

	/**
	 * Holds the value that is multiplied with the original values of core- and
	 * reachability distances in order to get better graphical views
	 */
	private int verticalAdjustment;

	/**
	 * Specifies the color for displaying core-distances
	 */
	private Color coreDistanceColor;

	/**
	 * Specifies the color for displaying reachability-distances
	 */
	private Color reachabilityDistanceColor;

	/**
	 * Specifies the width for displaying the distances
	 */
	private int widthSlider;

	/**
	 * Holds the flag for showCoreDistances
	 */
	private boolean showCoreDistances;

	/**
	 * Holds the flag for showrRechabilityDistances
	 */
	private boolean showReachabilityDistances;

	/**
	 * Holds the index of the last toolTip
	 */
	private int recentIndex = -1;

	// *****************************************************************************************************************
	// constructors
	// *****************************************************************************************************************

	public GraphPanel(FastVector resultVector, int verticalAdjustment,
			boolean showCoreDistances, boolean showReachbilityDistances) {
		this.resultVector = resultVector;
		this.verticalAdjustment = verticalAdjustment;
		coreDistanceColor = new Color(100, 100, 100);
		reachabilityDistanceColor = Color.orange;
		widthSlider = 5;
		this.showCoreDistances = showCoreDistances;
		this.showReachabilityDistances = showReachbilityDistances;

		addMouseMotionListener(new MouseHandler());
	}

	// *****************************************************************************************************************
	// methods
	// *****************************************************************************************************************

	/**
	 * Draws the OPTICS Plot
	 * 
	 * @param g
	 */
	protected void paintComponent(Graphics g) {
		if (isOpaque()) {
			Dimension size = getSize();
			g.setColor(getBackground());
			g.fillRect(0, 0, size.width, size.height);
		}

		int stepSize = 0;
		int cDist = 0;
		int rDist = 0;

		for (int vectorIndex = 0; vectorIndex < resultVector.size(); vectorIndex++) {
			double coreDistance = ((DataObject) resultVector
					.elementAt(vectorIndex)).getCoreDistance();
			double reachDistance = ((DataObject) resultVector
					.elementAt(vectorIndex)).getReachabilityDistance();

			if (coreDistance == DataObject.UNDEFINED)
				cDist = getHeight();
			else
				cDist = (int) (coreDistance * verticalAdjustment);

			if (reachDistance == DataObject.UNDEFINED)
				rDist = getHeight();
			else
				rDist = (int) (reachDistance * verticalAdjustment);

			int x = vectorIndex + stepSize;

			if (isShowCoreDistances()) {
				/**
				 * Draw coreDistance
				 */
				g.setColor(coreDistanceColor);
				g.fillRect(x, getHeight() - cDist, widthSlider, cDist);
			}

			if (isShowReachabilityDistances()) {
				int sizer = widthSlider;
				if (!isShowCoreDistances())
					sizer = 0;
				/**
				 * Draw reachabilityDistance
				 */
				g.setColor(reachabilityDistanceColor);
				g.fillRect(x + sizer, getHeight() - rDist, widthSlider, rDist);
			}

			if (isShowCoreDistances() && isShowReachabilityDistances()) {
				stepSize += (widthSlider * 2);
			} else
				stepSize += widthSlider;
		}
	}

	/**
	 * Sets a new resultVector
	 * 
	 * @param resultVector
	 */
	public void setResultVector(FastVector resultVector) {
		this.resultVector = resultVector;
	}

	/**
	 * Displays a toolTip for the selected DataObject
	 * 
	 * @param toolTip
	 */
	public void setNewToolTip(String toolTip) {
		setToolTipText(toolTip);
	}

	/**
	 * Adjusts the size of this panel in respect of the shown content
	 * 
	 * @param serObject
	 *            SERObject that contains the OPTICS clustering results
	 */
	public void adjustSize(SERObject serObject) {
		int i = 0;
		if (isShowCoreDistances() && isShowReachabilityDistances())
			i = 10;
		else if ((isShowCoreDistances() && !isShowReachabilityDistances())
				|| !isShowCoreDistances() && isShowReachabilityDistances())
			i = 5;
		setSize(new Dimension((i * serObject.getDatabaseSize())
				+ serObject.getDatabaseSize(), getHeight()));
		setPreferredSize(new Dimension((i * serObject.getDatabaseSize())
				+ serObject.getDatabaseSize(), getHeight()));
	}

	/**
	 * Returns the flag for showCoreDistances
	 * 
	 * @return True or false
	 */
	public boolean isShowCoreDistances() {
		return showCoreDistances;
	}

	/**
	 * Sets the flag for showCoreDistances
	 * 
	 * @param showCoreDistances
	 */
	public void setShowCoreDistances(boolean showCoreDistances) {
		this.showCoreDistances = showCoreDistances;
	}

	/**
	 * Returns the flag for showReachabilityDistances
	 * 
	 * @return True or false
	 */
	public boolean isShowReachabilityDistances() {
		return showReachabilityDistances;
	}

	/**
	 * Sets the flag for showReachabilityDistances
	 * 
	 * @param showReachabilityDistances
	 */
	public void setShowReachabilityDistances(boolean showReachabilityDistances) {
		this.showReachabilityDistances = showReachabilityDistances;
	}

	/**
	 * Sets a new value for the vertical verticalAdjustment
	 * 
	 * @param verticalAdjustment
	 */
	public void setVerticalAdjustment(int verticalAdjustment) {
		this.verticalAdjustment = verticalAdjustment;
	}

	/**
	 * Sets a new color for the coreDistance
	 * 
	 * @param coreDistanceColor
	 */
	public void setCoreDistanceColor(Color coreDistanceColor) {
		this.coreDistanceColor = coreDistanceColor;
		repaint();
	}

	/**
	 * Sets a new color for the reachabilityDistance
	 * 
	 * @param reachabilityDistanceColor
	 */
	public void setReachabilityDistanceColor(Color reachabilityDistanceColor) {
		this.reachabilityDistanceColor = reachabilityDistanceColor;
		repaint();
	}

	// *****************************************************************************************************************
	// inner classes
	// *****************************************************************************************************************

	private class MouseHandler extends MouseMotionAdapter implements
			RevisionHandler {

		/**
		 * Invoked when the mouse button has been moved on a component (with no
		 * buttons no down).
		 */
		public void mouseMoved(MouseEvent e) {
			showToolTip(e.getX());
		}

		/**
		 * Shows a toolTip with the dataObjects parameters (c-dist, r-dist, key,
		 * attributes . . .)
		 * 
		 * @param x
		 *            MouseCoordinate X
		 * @return boolean
		 */
		private boolean showToolTip(int x) {
			int i = 0;
			if (isShowCoreDistances() && isShowReachabilityDistances())
				i = 11;
			else if ((isShowCoreDistances() && !isShowReachabilityDistances())
					|| !isShowCoreDistances() && isShowReachabilityDistances()
					|| !isShowCoreDistances() && !isShowReachabilityDistances())
				i = 6;
			if ((x / i) == recentIndex)
				return false;
			else
				recentIndex = x / i;
			DataObject dataObject = null;
			try {
				dataObject = (DataObject) resultVector.elementAt(recentIndex);
			} catch (Exception e) {
			}
			if (dataObject != null) {
				if (!isShowCoreDistances() && !isShowReachabilityDistances()) {
					setNewToolTip("<html><body><b>Please select a distance"
							+ "</b></body></html>");
				} else
					setNewToolTip("<html><body><table>"
							+ "<tr><td>DataObject:</td><td>"
							+ dataObject
							+ "</td></tr>"
							+ "<tr><td>Key:</td><td>"
							+ dataObject.getKey()
							+ "</td></tr>"
							+ "<tr><td>"
							+ (isShowCoreDistances() ? "<b>" : "")
							+ "Core-Distance:"
							+ (isShowCoreDistances() ? "</b>" : "")
							+ "</td><td>"
							+ (isShowCoreDistances() ? "<b>" : "")
							+ ((dataObject.getCoreDistance() == DataObject.UNDEFINED) ? "UNDEFINED"
									: Utils.doubleToString(
											dataObject.getCoreDistance(), 3, 5))
							+ (isShowCoreDistances() ? "</b>" : "")
							+ "</td></tr>"
							+ "<tr><td>"
							+ (isShowReachabilityDistances() ? "<b>" : "")
							+ "Reachability-Distance:"
							+ (isShowReachabilityDistances() ? "</b>" : "")
							+ "</td><td>"
							+ (isShowReachabilityDistances() ? "<b>" : "")
							+ ((dataObject.getReachabilityDistance() == DataObject.UNDEFINED) ? "UNDEFINED"
									: Utils.doubleToString(dataObject
											.getReachabilityDistance(), 3, 5))
							+ (isShowReachabilityDistances() ? "</b>" : "")
							+ "</td></tr>" + "</table></body></html>");
			}
			return true;
		}

		/**
		 * Returns the revision string.
		 * 
		 * @return the revision
		 */
		public String getRevision() {
			return RevisionUtils.extract("$Revision: 1.4 $");
		}
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 1.4 $");
	}
}
